﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace Jacobi.Seesmic.FeedReader.Utilities
{
    /// <summary>
    /// Observer changes in a collection and notifies one or more handlers.
    /// </summary>
    /// <typeparam name="T">The item type from the collection.</typeparam>
    internal sealed class CollectionObserver<T> : IDisposable
    {
        private INotifyCollectionChanged _collectionChanged;
        private List<Action<T>> _onItemAddedHandlers = new List<Action<T>>();
        private List<Action<T>> _onItemRemovedHandlers = new List<Action<T>>();

        /// <summary>
        /// Constructs an instance on the collection.
        /// </summary>
        /// <param name="notify">Must not be null.</param>
        public CollectionObserver(INotifyCollectionChanged notify)
        {
            _collectionChanged = notify;
            _collectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(NotifyCollectionChanged);
        }

        /// <summary>
        /// Event handler that is called when the collection has changed.
        /// </summary>
        /// <param name="sender">The collection.</param>
        /// <param name="e">Changed item information.</param>
        private void NotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    CallActions(_onItemAddedHandlers, e.NewItems);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    CallActions(_onItemRemovedHandlers, e.OldItems);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    CallActions(_onItemRemovedHandlers, e.OldItems);
                    CallActions(_onItemAddedHandlers, e.NewItems);
                    break;
                //case NotifyCollectionChangedAction.Reset:
                //    break;
            }
        }

        /// <summary>
        /// Notifies the specified handlers when the collection has changed.
        /// </summary>
        /// <param name="onItemAdded">Called when an item has been added to the collection. Can be null.</param>
        /// <param name="onItemRemoved">Called when an item has been removed from the collection. Can be null.</param>
        public void Observe(Action<T> onItemAdded, Action<T> onItemRemoved)
        {
            if (onItemAdded != null)
            {
                _onItemAddedHandlers.Add(onItemAdded);
            }

            if (onItemRemoved != null)
            {
                _onItemRemovedHandlers.Add(onItemRemoved);
            }
        }

        /// <summary>
        /// Helper to call all <paramref name="actions"/> for all items in <paramref name="collection"/>.
        /// </summary>
        /// <param name="actions">A collection of actions.</param>
        /// <param name="collection">A collection of items that have changed.</param>
        private static void CallActions(List<Action<T>> actions, IEnumerable collection)
        {
            foreach (Action<T> action in actions)
            {
                CallAction(action, collection);
            }
        }

        /// <summary>
        /// Helper to call the <paramref name="action"/> with all values in <paramref name="collection"/>.
        /// </summary>
        /// <param name="action">A handler.</param>
        /// <param name="collection">A collection of items that have changed.</param>
        private static void CallAction(Action<T> action, IEnumerable collection)
        {
            if (action != null && collection != null)
            {
                foreach (T item in collection)
                {
                    action(item);
                }
            }
        }

        /// <summary>
        /// Cleans up the object and unhooks the event handler from the collection being observed.
        /// </summary>
        public void Dispose()
        {
            if (_collectionChanged != null)
            {
                _collectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(NotifyCollectionChanged);
                _collectionChanged = null;
                _onItemAddedHandlers.Clear();
                _onItemRemovedHandlers.Clear();
            }
        }
    }
}